package showtime.framework.engine
{
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Multitouch;
	import flash.ui.MultitouchInputMode;
	import flash.utils.getTimer;
	
	import showtime.framework.engine.input.KeyboardDevice;
	import showtime.framework.engine.input.MouseDevice;
	
	/** 
	 * @project showtime
	 * @author yinhao
	 * @date 2011-5-26
	 *
	 */
	
	public class World2D extends Sprite
	{
		/**
		 * The frameRate when creating the world. This is the frameRate that the game will try 
		 * to set the player to be. 
		 */		
		public static var focusedFrameRate:int = 0;
			
		public static var unfocusedFrameRate:int = 0;
		/**
		 * The stack of scenes in the game. The top scene is always updated and rendered.
		 * Scenes that are not the top scene will only be updated if persistantUpdate is true, and will only 
		 * rendered if persistantRender is true. 
		 */		
		public static var scenes:Vector.<Scene2D> = null;
		/**
		 * The root flash Stage object. 
		 */		
		public static var stage:Stage = null;
		/**
		 * The root World2D object. 
		 */		
		public static var root:World2D = null;
		/**
		 * The timestamp for the current frame. 
		 */		
		public static var now:int = 0;
		/**
		 * The timestamp for the previous frame. 
		 */		
		public static var then:int = 0;
		/**
		 * The amount of time that has passed between the last frame and the current frame.
		 * You should always multiply by this when moving objects based on velocity.
		 * This way, even if the fps rate drops, your movement will still be consistent. 
		 */		
		public static var elapsed:Number = 0;
		/**
		 * Width of the world. specified in the super of your main class.
		 */		
		public static var width:int = 0;
		/**
		 * Height of the world, specified in the super of your main class. 
		 */		
		public static var height:int = 0;
		
		public static var keyboard:KeyboardDevice = null;
		
		public static var mouse:MouseDevice = null;
		/**
		 * The initialize scene that the game will begin in. 
		 */		
		private static var initializeScene:Class = null;	
		
		public function World2D(sizeX:int, sizeY:int, initializeScene:Class = null, frameRate:int = 30)
		{
			World2D.focusedFrameRate = frameRate;
			World2D.unfocusedFrameRate = frameRate >> 1;
			World2D.initializeScene = initializeScene;
			World2D.scenes = new Vector.<Scene2D>();
				
			addEventListener(Event.ADDED_TO_STAGE, onStageInitialized);
		}
			
		private function onStageInitialized(event:Event):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, onStageInitialized);
			
			configRoot();
			
			configStage();
			
		}
		
		private function configRoot():void
		{
			keyboard = new KeyboardDevice();
			mouse = new MouseDevice();
			
			stage.addEventListener(Event.DEACTIVATE, onDeactivate);
			stage.addEventListener(Event.ACTIVATE, onActivate);
			
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyboard.onKeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyboard.onKeyUp);
			
			stage.addEventListener(MouseEvent.MOUSE_DOWN, mouse.onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, mouse.onMouseUp);
		}
			
		private function configStage():void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			World2D.stage = stage;
			World2D.root = this;		
			
			Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
			
			initialize();
			
			create();
			
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		private function initialize():void
		{
			stage.frameRate = focusedFrameRate;
			
			World2D.width = width == 0 ? stage.stageWidth : width;
			World2D.height = height == 0 ? stage.stageHeight : height;		
			
			if (initializeScene != null)
			{
				pushScene(new initializeScene());
			}
		}
		
		/**
		 * The main game loop callback that is executed once per frame. 
		 * Handles updating the world logic.
		 * 
		 * @param evt
		 * 
		 */		
		private function enterFrameHandler(evt:Event):void
		{		
			then = now;
			now = getTimer();
			elapsed = then == 0 ? 0 : (now - then) / 1000;

			update();			
			render();

		}
		
		/**
		 * Updates the active scenes. 
		 * 
		 */		
		private function update():void
		{
			var scene:Scene2D = null;
			for (var index:int = 0; index < scenes.length; index++)
			{
				scene = scenes[index];
				
				if (index == scenes.length - 1)
				{
					scene.update();
				}
			}
		}
		
		/**
		 * Renders the active scenes. 
		 * 
		 */		
		private function render():void
		{
			var scene:Scene2D = null;
			for (var index:int = 0; index < scenes.length; index++)
			{
				scene = scenes[index];
				
				if (index == scenes.length - 1)
				{
					scene.render();
				}
			}
		}
	
		protected function onActivate(event:Event):void
		{
			stage.frameRate = focusedFrameRate;
		}
		
		protected function onDeactivate(event:Event):void
		{
			keyboard.releaseAll();
			stage.frameRate = unfocusedFrameRate;
		}
	
		/**
		 * This function is called once the engine is completely set up.  Any initialization (such as initializing a dialog
		 * system), should also occur in this function. This ensures that the stage is set up before any graphics are uploaded
		 * to the CPU. 
		 */	
		public function create():void
		{
		
		}
		
		public function destroy():void
		{
			
		}
		
		public static function pushScene(scene:Scene2D):Scene2D
		{		
			scenes.push(scene);
			scene.create();
			root.addChild(scene);
			return scene;
		}
		
		public static function popScene():void
		{
			var scene:Scene2D = scenes.pop();
			
			if (scene != null)
			{
				scene.destroy();
			}
			
		}
		
		public static function switchScene(scene:Scene2D):Scene2D
		{
			popScene();
			return pushScene(scene);
		}
		
		public static function get scene():Scene2D
		{
			if (scenes.length <= 0) 
			{
				return null;
			}
			return scenes[scenes.length - 1];
		}
	}
}